﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace Model
{

    #region 接口

    /// <summary>
    /// 对象事件接口
    /// </summary>
    public interface IObjectEvent
    {
        /// <summary>
        /// 获取相关联类的类型
        /// </summary>
        /// <returns></returns>
        Type Type();

        /// <summary>
        /// 设置相关联对象
        /// </summary>
        /// <param name="value"></param>
        void Set(object value);
    }

    #endregion

    public abstract class ObjectEvent<T> : IObjectEvent
    {
        // 相关联的对象实例
        private T value;

        public void Set(object value)
        {
            this.value = (T)value;
        }

        /// <summary>
        /// 获取实例
        /// </summary>
        /// <returns></returns>
        protected T Get()
        {
            return value;
        }

        /// <summary>
        /// 获取当前事件对象的类型
        /// </summary>
        /// <returns></returns>
        public Type Type()
        {
            return typeof(T);
        }
    }

    /// <summary>
    /// 对象事件
    /// </summary>
    public  class ObjectEvents : Singleton<ObjectEvents>
    {

        /// <summary>
        /// 所有需要加载的程序集
        /// </summary>
        private Dictionary<string, Assembly> assemblys = new Dictionary<string, Assembly>();

        /// <summary>
        /// 处置器事件字典
        /// K:是事件对象关联的类型
        /// </summary>
        private Dictionary<Type, IObjectEvent> disposerEvents = new Dictionary<Type, IObjectEvent>();

        private Queue<Disposer> loaders = new Queue<Disposer>();
        private Queue<Disposer> starts = new Queue<Disposer>();
        private List<Disposer> updates = new List<Disposer>();

        /// <summary>
        /// 添加一个程序集
        /// </summary>
        /// <param name="name"></param>
        /// <param name="assembly"></param>
        public void Add(string name, Assembly assembly)
        {
            this.assemblys[name] = assembly;
            foreach (Type type in assembly.GetTypes())
            {
                ObjectEventAttribute attr = type.GetCustomAttribute<ObjectEventAttribute>(false);
                if (attr == null) continue;

                object obj = Activator.CreateInstance(type);

                IObjectEvent objectEvent = obj as IObjectEvent;

                if (objectEvent == null)
                {
                    Log.Error($"组件事件没有继承IObjectEvent：{type.Name}");
                    continue;
                }

                this.disposerEvents[objectEvent.Type()] = objectEvent;
            }
            this.Load();
        }

        /// <summary>
        /// 获取所有程序集
        /// </summary>
        /// <returns></returns>
        public Assembly[] GetAll()
        {
            return this.assemblys.Values.ToArray();
        }

        /// <summary>
        /// 添加处置器
        /// </summary>
        /// <param name="disposer"></param>
        public void Add(Disposer disposer)
        {
            if(!this.disposerEvents.TryGetValue(disposer.GetType(),out IObjectEvent objectEvent))
            {
                return;
            }

            if(objectEvent is ILoad)
            {
                this.loaders.Enqueue(disposer);
            }

            if (objectEvent is IStart)
                this.starts.Enqueue(disposer);

            if(objectEvent is IUpdate)
            {
                this.updates.Add(disposer);
            }
        }

        /// <summary>
        /// 用在服务端热更新。
        /// 就是说在服务端重新加载Hotfix程序集的时候，loader里面是存在处置器的。
        /// 这个时候一些组件的Load就会被调用，做一些重新加载后的处理。
        /// </summary>
        public void Load()
        {
            while (this.loaders.Count > 0)
            {
                Disposer disposer = this.loaders.Dequeue();
                if(disposer.Id == 0)
                {
                    continue;
                }

                // 不存在
                if(!this.disposerEvents.TryGetValue(disposer.GetType(),out IObjectEvent objectEvent))
                {
                    continue;
                }

                ILoad load = disposer as ILoad;
                if(load == null)
                {
                    continue;
                }

                objectEvent.Set(disposer);
                try
                {
                    load.Load();
                }
                catch (Exception e)
                {
                    Log.Error(e.Message);
                }
            } 
        }

        /// <summary>
        /// 唤醒
        /// 在创建组件的时候，调用。
        /// </summary>
        /// <param name="disposer"></param>
        public void Awake(Disposer disposer)
        {
            Type t = disposer.GetType();
            if (!this.disposerEvents.TryGetValue(t,out IObjectEvent objectEvent))
            {
                return;
            }

            IAwake iAwake = objectEvent as IAwake;
            if(iAwake == null)
            {
                return;
            }

            objectEvent.Set(disposer);
            iAwake.Awake();
        }

        public void Awake<P1>(Disposer disposer,P1 p1)
        {
            if (!this.disposerEvents.TryGetValue(disposer.GetType(), out IObjectEvent objectEvent))
                return;

            IAwake<P1> iAwake = objectEvent as IAwake<P1>;
            if (iAwake == null) return;

            objectEvent.Set(disposer);
            iAwake.Awake(p1);
        }

        public void Awake<P1,P2>(Disposer disposer, P1 p1,P2 p2)
        {
            if (!this.disposerEvents.TryGetValue(disposer.GetType(), out IObjectEvent objectEvent))
                return;

            IAwake<P1, P2> iAwake = objectEvent as IAwake<P1, P2>;
            if (iAwake == null) return;

            objectEvent.Set(disposer);
            iAwake.Awake(p1, p2);
        }

        public void Awake<P1, P2, P3>(Disposer disposer, P1 p1,P2 p2,P3 p3)
        {
            if (!this.disposerEvents.TryGetValue(disposer.GetType(), out IObjectEvent objectEvent))
                return;

            IAwake<P1, P2, P3> iAwake = objectEvent as IAwake<P1, P2, P3>;
            if (iAwake == null) return;

            objectEvent.Set(disposer);
            iAwake.Awake(p1, p2, p3);
        }

        /// <summary>
        /// 调用开始方法
        /// </summary>
        public void Start()
        {
            while (starts.Count > 0)
            {
                Disposer disposer = starts.Dequeue();

                if (!this.disposerEvents.TryGetValue(disposer.GetType(), out IObjectEvent objectEvent)) return;

                IStart iStart = objectEvent as IStart;
                if (iStart == null) return;

                objectEvent.Set(disposer);
                iStart.Start();
            }
        }

        /// <summary>
        /// Update调用
        /// </summary>
        public void Update()
        {
            this.Start();

            foreach (var  disposer   in updates)
            {
                if (disposer.Id == 0)
                    continue;

                if (!this.disposerEvents.TryGetValue(disposer.GetType(), out IObjectEvent objectEvent))
                    continue;

                IUpdate iUpdate = objectEvent as IUpdate;

                if (iUpdate == null)
                    continue;

                try
                {
                    iUpdate.Update();
                }
                catch (Exception e)
                {
                    Log.Error(e.Message);
                }
            }
        }

        public static void Close()
        {
            _instance = null;
        }
    }
}